package edu.umn.csci5511.facedetection.utilities;

import edu.umn.csci5511.facedetection.colorspace.ColorRGB;
import edu.umn.csci5511.facedetection.colorspace.ColorYCbCr;
import edu.umn.csci5511.facedetection.colorspace.ColorYCbCr1;

public class ImageUtils {
	static float a_cb_y = 0;
	static float b_cb_y = 0;
	static float a_cr_y = 0;
	static float b_cr_y = 0;
	static float a_w_cby = 0;
	static float b_w_cby = 0;
	static float a_w_cry = 0;
	static float b_w_cry = 0;
	
	static {
		int k_h = 188;
		int k_l = 125;
		int y_min = 16;
		int y_max = 235;
		float w_hcb = 14;
		float w_hcr = 10;
		float w_lcb = 23;
		float w_lcr = 20;
		float w_cb = 46.97f;
		float w_cr = 38.76f;
		
		float cb_kh = 108;
		float cr_kh = 154;

		//cb_y = 108 + (((k_l - y)*(118 - 108))/(k_l - y_min));
		//w_cby = w_lcb + ((y - y_min)*(w_cb - w_lcb))/(k_l - y_min);
		b_cb_y = (118 - 108)/(k_l - y_min);
		a_cb_y = 108 + k_l*b_cb_y;
		b_w_cby = (w_cb - w_lcb)/(k_l - y_min);
		a_w_cby = w_lcb - y_min*b_w_cby;
		
		//cr_y = 154 - (((k_l - y)*(154 - 144))/(k_l - y_min));
		//w_cry = w_lcr + ((y - y_min)*(w_cr - w_lcr))/(k_l - y_min);
		b_cr_y = (154 - 144)/(k_l - y_min);
		a_cr_y = 154 - k_l*b_cr_y;
		b_w_cry = (w_cr - w_lcr)/(k_l - y_min);
		a_w_cry = w_lcr - y_min*b_w_cry;
		
		
		/*
		 * 108 is the value of cb_y for k_h for which we don't have a formula in cb_y.
		 * So, I took the  one that is for k_l<y<k_h.
		 */
//		float a = cb_y*(w_cb/w_cby) + cb_kh;
//		cr1 = (cr - cr_y)*(w_cr/w_cry) + cr_kh;
//		} else if(y > k_h) {
//			cb_y = 108 + (((y - k_h)*(118 - 108))/(y_max - k_h));
//			cr_y = 154 + (((y - k_h)*(154 - 132))/(y_max - k_h));
//			w_cby = w_hcb + ((y_max - y)*(w_cb - w_hcb))/(y_max - k_h);
//			w_cry = w_hcr + ((y_max - y)*(w_cr - w_hcr))/(y_max - k_h);
//			/*
//			 * 108 is the value of cb_y for k_h for which we don't have a formula in cb_y.
//			 * So, I took the  one that is for k_l<y<k_h.
//			 */
//			cb1 = (cb - cb_y)*(w_cb/w_cby) + cb_kh;
//			cr1 = (cr - cr_y)*(w_cr/w_cry) + cr_kh;
//		}
	}
	
	public static ColorYCbCr fromRGBtoYCbCr(ColorRGB colorRGB) {
		return fromRGBtoYCbCr(colorRGB.getR(), colorRGB.getG(), colorRGB.getB());
	}
	
	//TODO check the range of YCbCr and sure its (16-235). approximate to closed byte once we are sure
	public static ColorYCbCr fromRGBtoYCbCr(int r, int g, int b) {
		/*TODO we can save r values in an static hash map, which will be used over the entire image set.
		 * So that we can save all these calculations in a map, as r values can range over 0-256, our hashmap lookup will become 
		 * faster over time.
		 */
		int y = (int)(16 + .257f*r + .504f*g + .098f*b);
		int cb = (int)(128 - .148f*r - .291f*g + .439f*b);
		int cr = (int)(128 + .439f*r - .368f*g - .071f*b);
		if(y<16 || y>235) {
			System.out.println("R:" + r + " G:" + g + " B:" + b);
			System.out.println("Y out of range:" + y);
		}
		if(cb<16 || cb>240) {
			System.out.println("cb out of range:" + cb);
		}
		if(cr<16 || cr>240) {
			System.out.println("cr out of range:" + cr);
		}
		return new ColorYCbCr(y, cb, cr);
	}
	
	public static ColorYCbCr1 fromYCbCrToYCbCr1(ColorYCbCr colorYCbCr) {
		return fromYCbCrToYCbCr1(colorYCbCr.getY(), colorYCbCr.getCb(), colorYCbCr.getCr());
	}

	public static ColorYCbCr1 fromYCbCrToYCbCr1(float y, float cb, float cr) {
		int k_h = 188;
		int k_l = 125;
		int y_min = 16;
		int y_max = 235;
		float w_hcb = 14;
		float w_lcb = 23;
		float w_hcr = 10;
		float w_lcr = 20;
		float w_cb = 46.97f;
		float w_cr = 38.76f;
		
		float cb_y;
		float cr_y;
		float cb_kh = 108;
		float cr_kh = 154;
		float w_cby;
		float w_cry;
		//Default values of cb_y and cr_y if k_l<y<k_h
		float cb1 = cb;
		float cr1 = cr;
		if(y < k_l) {
			cb_y = 108 + (((k_l - y)*(118 - 108))/(k_l - y_min));
			cr_y = 154 - (((k_l - y)*(154 - 144))/(k_l - y_min));
			w_cby = w_lcb + ((y - y_min)*(w_cb - w_lcb))/(k_l - y_min);
			w_cry = w_lcr + ((y - y_min)*(w_cr - w_lcr))/(k_l - y_min);
			/*
			 * 108 is the value of cb_y for k_h for which we don't have a formula in cb_y.
			 * So, I took the  one that is for k_l<y<k_h.
			 */
			cb1 = (cb - cb_y)*(w_cb/w_cby) + cb_kh;
			cr1 = (cr - cr_y)*(w_cr/w_cry) + cr_kh;
		} else if(y > k_h) {
			cb_y = 108 + (((y - k_h)*(118 - 108))/(y_max - k_h));
			cr_y = 154 + (((y - k_h)*(154 - 132))/(y_max - k_h));
			w_cby = w_hcb + ((y_max - y)*(w_cb - w_hcb))/(y_max - k_h);
			w_cry = w_hcr + ((y_max - y)*(w_cr - w_hcr))/(y_max - k_h);
			/*
			 * 108 is the value of cb_y for k_h for which we don't have a formula in cb_y.
			 * So, I took the  one that is for k_l<y<k_h.
			 */
			cb1 = (cb - cb_y)*(w_cb/w_cby) + cb_kh;
			cr1 = (cr - cr_y)*(w_cr/w_cry) + cr_kh;
		}
		
		if(y<16 || y>235) {
			System.out.println("Y out of range ':" + y);
		}
		if(cb1<16 || cb1>240) {
			System.out.println("cb out of range ':" + cb1);
		}
		if(cr1<16 || cr1>240) {
			System.out.println("cr out of range ':" + cr1);
		}
		return new ColorYCbCr1(y, cb1, cr1);
	}
	
	//TODO to check if we can use the float here in place of double.
	//I really want to use double size memory when I don't even need it.
	public static boolean isSkinColor(ColorYCbCr1 colorYCbCr1) {
		float a = 25.39f;
		float b = 14.03f;
		float ecx = 1.60f;
		float ecy = 2.41f;
		float theta = 2.53f;
		float cx = 109.38f;
		float cy = 152.02f;
		double x = Math.cos(theta)*(colorYCbCr1.getCb1() - cx) + Math.sin(theta)*(colorYCbCr1.getCr1() - cy);
		double y = Math.cos(theta)*(colorYCbCr1.getCr1() - cy) - Math.sin(theta)*(colorYCbCr1.getCb1() - cx);
		
		double classifier = Math.pow((x - ecx)/a, 2) + Math.pow((y - ecy)/b, 2);
		if(classifier < 1) {
			return true;
		}
		else {
			return false;
		}
	}
	
	public double getEyeMapCComponent(ColorYCbCr ycbcr){
		double eyemapc = 0.0;
		double crComplementSquare = (ycbcr.getCr() < 255)?((255 - ycbcr.getCr())*(255 - ycbcr.getCr())):0;
		double cbSquare = ycbcr.getCb() * ycbcr.getCb();
		double cbDivCrSqaure = 0.0;
		if(ycbcr.getCr() > 0){
			cbDivCrSqaure = (ycbcr.getCb()/ycbcr.getCr());
		}
		eyemapc = (cbSquare + crComplementSquare + cbDivCrSqaure)/3;
		return eyemapc;
	}
}